/**
 *******************************************************************************
 * @Brief     : Reset Clock Control(RCC) implementation.
 * @FileName  : tlsr825x_rcc.c
 * @Author    : GaoQiu
 * @CreateDate: 2020-09-10
 * @Copyright : Copyright(C) GaoQiu
 *              All Rights Reserved.
 *
 *******************************************************************************
 *
 * The information contained herein is confidential and proprietary property of
 * GaoQiu and is available under the terms of Commercial License Agreement
 * between GaoQiu and the licensee in separate contract or the terms described
 * here-in.
 *
 * This heading MUST NOT be removed from this file.
 *
 * Licensees are granted free, non-transferable use of the information in this
 * file under Mutual Non-Disclosure Agreement. NO WARRENTY of ANY KIND is provided.
 *
 *******************************************************************************
 */
#include "tlsr825x.h"
#include "tlsr825x_rcc.h"

const uint32_t fhsClkRate[] = {
	[0] = 48000000, /* PLL 48M  */
	[1] = 24000000, /* RC 24M   */
	[2] = 24000000, /* 24M XTAL */
	[3] = 24000000, /* 24M XTAL */
};

typedef struct{
	uint32_t  FhsRate;  /*!< Hz */
	uint32_t  SysRate;  /*!< Hz */
	uint8_t   FhsMux;  /*!< 0:48M PLL, 1: 24M RC, 2or3: 24M XTAL */
	uint8_t   SysClkMux;
	uint8_t   DivFhs;
}clk_t;

static clk_t clk;

static void RCC_UpdClockTree(void)
{
	clk.FhsMux = (CLOCK->CLKSEL_L >> 7) | ((CLOCK->CLKSEL_H & 0x01)<<1);
	clk.FhsRate = fhsClkRate[clk.FhsMux];

	clk.SysClkMux = ((CLOCK->CLKSEL_L >> 5) & 0x03);

	/* sys_clk = 24M RC */
	if(clk.SysClkMux == 0x00){
		clk.SysRate = 24000000;
	}
	/* sys_clk = fhs */
	else if(clk.SysClkMux == 0x01){
		clk.SysRate = clk.FhsRate;
	}
	/* sys_clk = fhs /div */
	else if(clk.SysClkMux == 0x02)
	{
		clk.DivFhs = CLOCK->CLKSEL_L & 0x0f; //!!! FHS div must exteed 1.
		clk.SysRate = clk.FhsRate / clk.DivFhs;
	}
	/* sys_clk = fhs /div_2_3 */
	else{
		clk.SysRate = 32000000;
	}
}

/**
 * @brief : Get FHS clock rate.
 *
 * @param : none.
 *
 * @return: none.
 */
uint32_t RCC_GetFhsClk(void)
{
	return clk.FhsRate;
}

/**
 * @brief : Get system clock rate.
 *
 * @param : none.
 *
 * @return: none.
 */
uint32_t RCC_GetSysClk(void)
{
	return clk.SysRate;
}

/**
 * @brief : Reset specified peripheral.
 *
 * @param : periph    can be RCC_IIC/RCC_SPI ...
 *
 * @return: none.
 */
void RCC_RstPeriph(uint32_t periph)
{
	uint8_t *p =(uint8_t*)&RST->RST0;

	for(int i=0; i<3; i++, p++)
	{
		uint8_t tmp = (uint8_t)(periph >> (i*8));
		if(tmp)
		{
			*p |= tmp;
			*p  = 0x00;
		}
	}
}

/**
 * @brief : Reset all peripheral.
 *
 * @param : none.
 *
 * @return: none.
 */
void RCC_RstAllPeriph(void)
{
	RST->RST0 = 0xff;
	RST->RST1 = 0xff;
	RST->RST2 = 0xff;

	RST->RST0 = 0x00;
	RST->RST1 = 0x00;
	RST->RST2 = 0x00;
}

/**
 * @brief : Set FHS clock source.
 *
 * @param : fhsSrc    can be FHS_SRC_48M/FHS_SRC_24M_RC...
 *
 * @return: none.
 */
void RCC_SetFhs(uint8_t fhsSrc)
{
	uint8_t fhsL = CLOCK->CLKSEL_L;
	uint8_t fhsH = CLOCK->CLKSEL_H;

	fhsL &= ~(RCC_FHS_CLK_SEL_L | RCC_FHS_DIV);
	fhsL |= (fhsSrc & RCC_FHS_CLK_SEL_L);

	fhsH &= ~RCC_FHS_CLK_SEL_H;
	fhsH |= (fhsSrc & RCC_FHS_CLK_SEL_H);

	CLOCK->CLKSEL_L = fhsL;
	CLOCK->CLKSEL_H = fhsH;

	/* Upate Clock Tree */
	RCC_UpdClockTree();
}

/**
 * @brief : Set system clock source.
 *
 * @param : fhsSrc    can be SYS_CLK_SRC_24M_RC/SYS_CLK_SRC_FHS ...
 * @param : fhsDiv    must >2
 * @return: none.
 */
void RCC_SetSysClk(uint8_t sysSrc, uint8_t fhsDiv)
{
#if 0
	if(sysSrc == SYS_CLK_SRC_FHS_DIV && fhsDiv <= 2){
		DRIV_TRACK_ERR("FHS divider value error: fhsDiv = %d\r\n", fhsDiv);
		ASSERT(false, DRIV_ERR_INVALID_PARAM);
		return;
	}
#endif

	uint8_t sysClk = CLOCK->CLKSEL_L;

	sysClk &= ~(RCC_SYS_CLK_SEL | RCC_FHS_DIV);
	sysClk |= ( (sysSrc & RCC_SYS_CLK_SEL) | (fhsDiv & RCC_FHS_DIV) );

	CLOCK->CLKSEL_L = sysClk;

	/* Upate Clock Tree */
	RCC_UpdClockTree();
}

/**
 * @brief : Enable specified peripheral clock.
 *
 * @param : periph    can be RCC_IIC/RCC_SPI ...
 *
 * @return: none.
 */
void RCC_EnablePeriphClk(uint32_t periph)
{
	uint8_t *p = (uint8_t *)&CLOCK->CLKEN0;
	for(int i=0; i<3; i++, p++)
	{
		uint8_t tmp = (uint8_t)(periph >> (i*8));
		if(tmp)
		{
			*p |= tmp;
		}
	}
}

/**
 * @brief : Disable specified peripheral clock.
 *
 * @param : periph    can be RCC_IIC/RCC_SPI ...
 *
 * @return: none.
 */
void RCC_DisablePeriphClk(uint32_t periph)
{
	uint8_t *p = (uint8_t *)&CLOCK->CLKEN0;
	for(int i=0; i<3; i++, p++)
	{
		uint8_t tmp = (uint8_t)(periph >> (i*8));
		if(tmp)
		{
			*p &= ~tmp;
		}
	}
}

/**
 * @brief : Enable all peripheral clock.
 *
 * @param : none
 *
 * @return: none.
 */
void RCC_EnableAllPeriphClk(void)
{
	CLOCK->CLKEN0 = 0xFF;
	CLOCK->CLKEN1 = 0xFF;
	CLOCK->CLKEN2 = 0xFF;
}

/**
 * @brief : Set all peripheral clock to default value.
 *
 * @param : none
 *
 * @return: none.
 */
void RCC_SetAllPeriphClkToDef(void)
{
	CLOCK->CLKEN0 = 0x80;
	CLOCK->CLKEN1 = 0x00;
	CLOCK->CLKEN2 = 0x30;
}

/**
 * @brief : define peripheral clock configuration.
 *
 * @param : none
 *
 * @return: none.
 */
void RCC_DefInit(void)
{
	/* Reset all peripheral clock. */
	RCC_RstAllPeriph();

	/* Close all peripheral clock. */
	RCC_SetAllPeriphClkToDef();

	/* Enable analog register and system timer working clock.
	 * 1. system timer can be working only when RCC_RF and RCC_SYS_TIMER are set.
	 * 2. analog register can be working only when RCC_ALG,RCC_ALGM and RCC_AIF are set.
	 */
	RCC_EnablePeriphClk(RCC_AIF|RCC_ALG|RCC_ALGM|RCC_RF|RCC_SYS_TIMER|RCC_AES);

	analog_write(0x82, 0x64);  //poweron_dft 0x0c, BIT<6>:clk_24M_tosar_en;  BIT<5>:clk_48M_todig_en;  BIT<3>:pd_dccc_doubler, 0 for power on
	analog_write(0x34, 0x80);
	analog_write(0x0b, 0x38);  //BIT<2> is removed, so 0x38 equals 0x3c

	analog_write(0x8c, 0x02);  //poweron_dft: 0x00,  <1> set 1: reg_xo_en_clk_ana_ana, to enable external 24M crystal
	analog_write(0x02, 0xa2);  //poweron_dft: 0xa4, <2:0> ret_ldo_trim,  set 0x02: 0.8V

#if 1
	/* DMA High Address */
	REG_32(0xc40) = 0x04040404;
	REG_32(0xc44) = 0x04040404;
	REG_8(0xc48)  = 0x04;

	if(REG_8(0x7d) == 0x01){   //kite A1
		analog_write(0x01, 0x3c);
	}
	else{ // kite A2 or above
		analog_write(0x01, 0x4c);
	}
#endif
}

/**
 * @brief : Select 32k clock source for DMIC.
 *
 * @param : none
 *
 * @return: none.
 */
void RCC_Set32kClk(uint8_t src32k)
{
	uint8_t tmp = CLOCK->DMIC_32K_SRC_SEL;
	tmp &= ~RCC_32K_SRC_SEL;
	tmp |= (src32k & RCC_32K_SRC_SEL);
	CLOCK->DMIC_32K_SRC_SEL = tmp;

	if(src32k == LSC_32K_SRC_XTAL){
		analog_write(0x05, analog_read(0x05) & ~BIT(1));//Power on 32k XTAL
		//TODO: kick 32k XTAL;
	}
	else{
		//Power on 32k RC; default power on.
	}
}

/**
 * @brief : Set DMIC clock.
 *
 * @param : none
 *
 * @return: none.
 */
void RCC_SetDmicClk(uint8_t dmicClkSrc, uint8_t step, uint8_t mod)
{
	uint8_t tmp = CLOCK->DMIC_32K_SRC_SEL;
	tmp &= ~RCC_DMIC_SRC_SEL;
	tmp |= (dmicClkSrc & RCC_DMIC_SRC_SEL);
	CLOCK->DMIC_32K_SRC_SEL = tmp;

	if((dmicClkSrc & RCC_DMIC_SRC_SEL) == DMIC_CLK_SRC_DIV)
	{
		CLOCK->DMIC_STEP = step;
		CLOCK->DMIC_MOD  = mod;
	}
}

/**
 * @brief : Set IIS clock. must set FHS to 48M.
 *
 * @param : none
 *
 * @return: none.
 */
void RCC_SetI2sClk(uint8_t step, uint8_t mod)
{
	CLOCK->I2S_STEP = step;
	CLOCK->I2S_MOD = mod;
}

